package cn.yangliu.nacos.comm.handler.flux;

import java.util.Objects;

import cn.yangliu.nacos.comm.JsonResult;
import cn.yangliu.nacos.comm.enums.GlobalResultCode;
import cn.yangliu.nacos.comm.ex.GlobalException;
import cn.yangliu.nacos.comm.handler.ExceptionResolver;
import cn.yangliu.nacos.comm.tools.JsonUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.netty.ByteBufFlux;

/**
 * weblux 异常处理
 * The type of nacos.
 *
 * @author 问道于盲
 * @date 2020 -01-05
 */
@Slf4j
@ConditionalOnClass(Mono.class)
@Component
public class GloablExceptionHandler implements ErrorWebExceptionHandler, ExceptionResolver {

    /**
     * Handle mono.
     *
     * @param exchange the exchange
     * @param e        the e
     * @return the mono
     */
    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable e) {
        log.error(e.getMessage(), e);
        HttpStatus status = null;
        JsonResult<Object> body = null;

        if (e instanceof GlobalException) {
            GlobalException exception = (GlobalException) e;
            JsonResult<Object> result = JsonResult.getInstance(exception.getCode(), exception.getMsg());
            return write(exchange, result, HttpStatus.valueOf(exception.getCode()));
        }
        /**
         * if current exception's cause is an instance of SaeException
         *
         * try to get SaeException from current exception's cause
         * and get message from SaeException
         *
         * if not , continue check
         */
        Throwable cause = e.getCause();
        if (Objects.nonNull(cause) && (cause instanceof GlobalException)) {
            status = HttpStatus.BAD_REQUEST;
            GlobalException exception = (GlobalException) cause;
            body = JsonResult.getInstance(exception.getCode(), cause.getMessage());
        }
        /**
         * Handle particular exceptions below
         */
        else if (e instanceof ConversionNotSupportedException
                || e instanceof HttpMessageNotWritableException) {
            status = HttpStatus.INTERNAL_SERVER_ERROR;
            body = JsonResult.getInstance(GlobalResultCode.PARAM_IS_INVALID);
        } else if (e instanceof HttpMessageNotReadableException
                || e instanceof MethodArgumentTypeMismatchException) {
            status = HttpStatus.BAD_REQUEST;
            body = JsonResult.getInstance(GlobalResultCode.PARAM_IS_INVALID);
        } else if (e instanceof TypeMismatchException) {
            status = HttpStatus.BAD_REQUEST;
            body = JsonResult.getInstance(GlobalResultCode.PARAM_TYPE_BIND_ERROR);
        } else if (e instanceof BindException) {
            status = HttpStatus.BAD_REQUEST;
            body = handleBindingResult(((BindException) e).getBindingResult());
        } else if (e instanceof MethodArgumentNotValidException) {
            status = HttpStatus.BAD_REQUEST;
            body = handleBindingResult(
                    ((MethodArgumentNotValidException) e).getBindingResult());
        }  else if (e instanceof AsyncRequestTimeoutException) {
            status = HttpStatus.SERVICE_UNAVAILABLE;
            body = JsonResult.getInstance(GlobalResultCode.INTERFACE_REQUEST_TIMEOUT);
        } else if (e instanceof EmptyResultDataAccessException) {
            status = HttpStatus.NOT_FOUND;
            body = JsonResult.getInstance(GlobalResultCode.DATA_NOT_EXIST);
        }  else {
            /**
             * Unknown exception, typically a wrapper with a common MVC
             * exception as cause (since @ExceptionHandler type declarations
             * also match first-level causes):
             * We only deal with top-level MVC exceptions here, so let's just
             * return 500 HTTP status
             */
            body = httpStatusToSaeResult(HttpStatus.INTERNAL_SERVER_ERROR);
        }

       return write(exchange ,body, status);

    }

    /**
     * Write mono.
     *
     * @param exchange the exchange
     * @param result   the result
     * @param status   the status
     * @return the mono
     */
    @SneakyThrows
    private  Mono<Void> write(ServerWebExchange exchange, JsonResult<?> result , HttpStatus status) {
        ServerHttpResponse response = exchange.getResponse();

        ObjectMapper objectMapper = JsonUtils.getObjectMapper();
        byte[] bytes = objectMapper.writeValueAsBytes(result);
        //设置http响应状态码
        response.setStatusCode(status);
        //设置响应头信息Content-Type类型
        response.getHeaders().add("Content-Type", "application/json");
        //设置返回json数据
        return response.writeAndFlushWith(
                Flux.just(ByteBufFlux.just(response.bufferFactory().wrap(bytes))));
    }

}
